using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UObject = UnityEngine.Object;

/// <summary>
/// 用于更新完成后，AB包各类型资源加载
/// </summary>
public class ResourceManager : MonoBehaviour
{
    #region 解析版本依赖信息，存储包名，资源名及依赖信息，用于后续资源加载
    //定义类（为什么不用结构体？为什么用internal），存储解析出来的包的相关信息
    internal class BundleInfo
    {
        public string AssetName;
        public string BundleName;
        public List<string> Dependences;
    }

    //用于进行对象池管理
    internal class BundleData
    {
        public AssetBundle Bundle;
        public int Count;

        public BundleData(AssetBundle ab)
        {
            Bundle = ab;
            Count = 1;
        }
    }

    //存放ab包信息的集合
    private Dictionary<string, BundleInfo> m_BundleInfos = new Dictionary<string, BundleInfo>();
    //缓存ab包资源的集合（加载ab包的时候，如果有就直接用，没有再加载）
    private Dictionary<string, BundleData> m_AssetBundles = new Dictionary<string, BundleData>();
    //加载资源的时候用m_BundleInfos，需要用资源名作为键来寻找包；
    //缓存ab包的时候存的是包，内部可能不止包含一个资源（尽管本项目中是一个包对应一个资源），因此需要用包名作为键存储包



    //更新后第一步：解析版本信息，进行资源信息缓存（文件名、包名、依赖文件名），以及lua脚本文件名缓存
    //从Manager使用这个方法，因此访问权限为public，是提供给外部的接口
    public void ParseVersion()
    {
        //版本文件路径（使用BundleResourcePath，在编辑器播放与打包发布后 使用的bundle资源路径不同）
        string versionPath = Path.Combine(PathUtil.BundleResourcePath, AppConst.FileListName);
        //获取后进行读取(该方法是把文本文件每一行读取出来，因为写入的时候是一行一行写的)
        string[] data = File.ReadAllLines(versionPath);

        //解析版本信息
        for (int i = 0; i < data.Length; i++)
        {
            //实例化一个BundleInfo对象,用于存储解析出来的包的相关信息
            BundleInfo budleinfo = new BundleInfo();
            //分割data[i]这一行的字符串
            string[] info = data[i].Split('|');
            //把分割出来的信息存储到BundleInfo对象中
            budleinfo.AssetName = info[0];
            budleinfo.BundleName = info[1];
            //info[2]之后的是依赖信息
            //list本质是数组，当知道数据容量时，和数组一样，好处是后期的增删查改很方便
            budleinfo.Dependences = new List<string>(info.Length - 2);
            for (int j = 2; j < info.Length; j++)
            {
                budleinfo.Dependences.Add(info[j]);
            }
            //把解析出来的包的相关信息存储到m_bundleInfos字典中
            m_BundleInfos.Add(budleinfo.AssetName, budleinfo);

            //存储所有lua文件名，用于后续加载调用
            if (budleinfo.AssetName.IndexOf("LuaScripts") > 0)
            {
                Manager.Lua.LuaNames.Add(budleinfo.AssetName);
            }
        }
    }
    #endregion

    #region bundle及资源异步加载使用/编辑器环境下资源加载；函数封装；资源卸载
    /// <summary>
    ///提供一个外部调用的接口，因为如果在外部使用startcoroutine，不是很方便。用于加载资源
    /// </summary>
    /// <param name="assetName"></param>
    /// <param name="action"></param>
    private void LoadAsset(string assetName, Action<UObject> action = null)
    //因为通过下面的函数进行了封装，因此此函数访问权限变为私有
    {
#if UNITY_EDITOR
        if (AppConst.GameMode == GameMode.EditorMode)
            EditorLoadAsset(assetName, action);
        else
#endif
            StartCoroutine(LoadBundleAsync(assetName, action));
    }

    /// <summary>
    /// 从加载过的bundle中获取资源
    /// </summary>
    /// <param name="assetName"></param>
    /// <returns></returns>
    private BundleData GetBundle(string bundleName)
    {
        BundleData bundleData = null;
        if (m_AssetBundles.TryGetValue(bundleName, out bundleData))
        {
            //如果缓存中有bundle，那么从缓存中取一次证明我要用一次，因此计数加一次
            bundleData.Count++;
            return bundleData;
        }
        return null;
    }

    /// <summary>
    /// 异步加载资源,默认为action参数为null，因为递归调用的时候，只需要加载依赖的包，不用加载资源，不需要回调
    /// 如果缓存或者对象池中有，直接取出用，如果是从缓存中取出，添加一次引用计数（不要忘记为依赖包也添加引用计数）
    /// 引用计数减则是在gameobject被release的时候
    /// </summary>
    /// <param name="assetName">资源名</param>
    /// <param name="action">对资源的操作</param>
    /// <returns></returns>
    private IEnumerator LoadBundleAsync(string assetName, Action<UObject> action = null)
    {
        //根据资源名从m_bundleInfos字典中获取包名
        string bundleName = m_BundleInfos[assetName].BundleName;
        //根据包名拼接包路径
        string bundlePath = Path.Combine(PathUtil.BundleResourcePath, bundleName);
        //获取依赖
        List<string> dependences = m_BundleInfos[assetName].Dependences;
        //加载过的包，直接从缓存中获取
        BundleData bundleData = GetBundle(bundleName);
        if (bundleData == null)
        {
            //这里为什么要传入资源名？？？？
            UObject obj = Manager.Pool.Spawn("AssetBundle", assetName);
            if(obj != null)
            {
                //从对象池中取，对象池中因为引用计数都是0，因此存BundleData没意义，存的是AssetBundle
                AssetBundle ab = obj as AssetBundle;
                bundleData = new BundleData(ab);
            }
            else
            {
                //从文件中加载包
                AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(bundlePath);
                yield return request;
                bundleData = new BundleData(request.assetBundle);
            }
            //这段代码在有了对象池之后要拿到外面，因为不管是从缓存加载还是对象池还是文件，都需要添加引用计数/重新加载，放里面在从缓存中获取的时候无法添加计数
            //遍历依赖列表
            //if (dependences != null && dependences.Count > 0)
            //{
            //    for (int i = 0; i < dependences.Count; i++)
            //    {
            //        //递归调用，加载依赖包及其依赖包
            //        yield return LoadBundleAsync(dependences[i], null);
            //    }
            //}
            //加载完成后，存储到缓存中
            m_AssetBundles.Add(bundleName, bundleData);
        }
        if (dependences != null && dependences.Count > 0)
        {
            for (int i = 0; i < dependences.Count; i++)
            {
                //递归调用，加载依赖包及其依赖包
                yield return LoadBundleAsync(dependences[i], null);
            }
        }

        if (assetName.EndsWith(".unity"))
        {
            action?.Invoke(null);
            yield break;
        }

        //当传入的回调为空的时候，意味着是递归加载的依赖资源，就不需要再加载包内资源，只需要加载ab包即可（性能上的优化）
        if (action == null)
            yield break;

        //当传入的回调不为空的时候，加载资源
        AssetBundleRequest assetRequest = bundleData.Bundle.LoadAssetAsync(assetName);
        yield return assetRequest;

        //回调，目的是使用资源，回调使用委托实现，因为委托可以将函数作为参数传递
        //?.是空值合并运算符，该语法糖表示，如果action不为空，则调用action(assetRequest.asset)
        //该行代码表示在异步加载完成后，如何使用资源，能保证资源加载完后再使用
        Debug.Log("异步加载包与资源完成");
        action?.Invoke(assetRequest?.asset);
    }

    private void MinusOneBundleCount(string bundleName)
    {
        if (m_AssetBundles.TryGetValue(bundleName, out BundleData bundleData))
        {
            if (bundleData.Count > 0)
            {
                bundleData.Count--;
                Debug.Log("bundle引用计数减一：" + bundleName + "count：" + bundleData.Count);
            }
            if(bundleData.Count <= 0)
            {
                Manager.Pool.UnSpawn("AssetBundle", bundleName, bundleData.Bundle);
                m_AssetBundles.Remove(bundleName);
                Debug.Log("放入对象池：" + bundleName);
            }
        }

    }
    //减去ab包和依赖包的引用计数
    public void MinusBundleCount(string assetName)
    {
        string bundleName = m_BundleInfos[assetName].BundleName;
        List<string> dependences = m_BundleInfos[assetName].Dependences;

        MinusOneBundleCount(bundleName);

        if (dependences != null)
        {
            foreach(string dep in dependences)
            {
                string name = m_BundleInfos[dep].BundleName;
                MinusOneBundleCount(name);
                //string bundlename = m_BundleInfos[dep].AssetName;
                //MinusBundleCount(bundlename);
            }
        }
    }


#if UNITY_EDITOR
    /// <summary>
    /// 编辑器环境的资源加载
    /// </summary>
    /// <param name="assetName"></param>
    /// <param name="action"></param>
    private void EditorLoadAsset(string assetName, Action<UObject> action = null)
    {
        Debug.Log("这是 编辑器加载资源");
        UObject obj = UnityEditor.AssetDatabase.LoadAssetAtPath(assetName, typeof(UObject));
        if (obj == null)
        {
            Debug.LogError("编辑器加载资源-assetname不存在：" + assetName);
        }
        action?.Invoke(obj);
    }
#endif

    //提供一系列的加载资源的接口,简化调用资源时需要调用LoadAsset函数，并手动传入PathUtil.GetLuaPath(资源名）的操作
    //简化后只需要在调用函数时确认对应资源类型的函数，传入资源名与回调函数即可

    public void LoadUI(string assetName, Action<UObject> action = null)
    {
        LoadAsset(PathUtil.GetUIPath(assetName), action);
    }
    public void LoadMusic(string assetName, Action<UObject> action = null)
    {
        LoadAsset(PathUtil.GetMusicPath(assetName), action);
    }
    public void LoadSound(string assetName, Action<UObject> action = null)
    {
        LoadAsset(PathUtil.GetSoundPath(assetName), action);
    }
    public void LoadEffect(string assetName, Action<UObject> action = null)
    {
        LoadAsset(PathUtil.GetEffectPath(assetName), action);
    }
    public void LoadSprite(string assetName, Action<UObject> action = null)
    {
        LoadAsset(PathUtil.GetSpritePath(assetName), action);
    }
    public void LoadScene(string assetName, Action<UObject> action = null)
    {
        LoadAsset(PathUtil.GetScenePath(assetName), action);
    }
    public void LoadLua(string assetName, Action<UObject> action = null)
    {
        //在从bundle包中加载lua的逻辑与其他资源不同，需要先全部加载到缓存中再调用（防止出现异步加载还没完成就调用的情况），而不是自己输入文件名加载
        //传入的assetName来自于List<string> LuaNames，来自于filelist文件，因此不需要通过GetLuaPath获取完整相对路径
        LoadAsset(assetName, action);
    }
    //通用的加载预制体的方法，因此需要调用的时候手动传入路径
    public void LoadPrefeb(string assetName, Action<UObject> action = null)
    {
        LoadAsset(assetName, action);
    }

    public void UnloadBundle(UObject obj)
    {
        AssetBundle assetBundle = obj as AssetBundle;
        assetBundle.Unload(true);
    }

    //public void UnloadBundleTest()
    //{
    //    AssetBundle assetBundle = m_AssetBundles[m_BundleInfos["Assets/BuildResources/UI/Prefabs/LoginUI.prefab"].BundleName].Bundle;
    //    assetBundle.Unload(true);
    //}

    #endregion

    //Tag：卸载bunlde，待做

    private void Start()
    {
        Debug.Log("1.资源 start");
    }
    private void Awake()
    {
        Debug.Log("1.资源 awake");
    }


}
